﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using LogLib;
using KAImage.Config;

namespace KAImage.Algorithms
{
    /// <summary>
    /// 色相环上的颜色元素
    /// </summary>
    public enum HueCircleColors : int
    {
        Gray = -1,                                 // None
        Red = 0,                                   // 346°-15°
        RedYellow = 1,                          //16°-45°
        Yellow = 2,                               // 46°-75°
        YellowGreen = 3,                      // 76°-105°
        Green = 4,                               // 106°-135°
        GreenCyan = 5,                       // 136°-165°      
        Cyan = 6,                                // 166°-195°
        CyanBlue = 7,                         // 196°-225°      
        Blue = 8,                                 // 226°-255°
        BlueMagenta = 9,                   // 256°-285°
        Magenta = 10,                        // 286°-315°
        MagentaRed = 11,                  // 316°-345°
    }

    /// <summary>
    /// HSL模式下的主要颜色通道
    /// </summary>
    public enum PrimaryChannel : int
    {
        Gray = -2,                             // None 
        Total = -1,                            // All
        Red = 0,                               // 0°
        Yellow = 1,                           // 60°
        Green = 2,                           // 120°
        Cyan = 3,                             // 180°
        Blue = 4,                              // 240°
        Magenta = 5,                       // 300°
    }

    public class HSLColor
    {
        #region static fields
        /// <summary>
        /// 色相环上颜色的区间集合
        /// </summary>
        public static List<ColorRange> HueColors = new List<ColorRange>();

        public const int Degree = 256;

        private static readonly short[, ,] HueMap = new short[Degree, Degree, Degree];

        public static void ThrowExceptionForInvalidHue(int hue)
        {
            if (hue < 0 && hue > ColorRange.CircleLength)
            {
                throw new Exception("参数取值不在色相范围[0,360]之内");
            }
        }

        public static void ThrowExceptionForInvalidDelta(int delta)
        {
            if (delta < -180 && delta > 180)
            {
                throw new Exception("参数取值不在色相范围[-180,180]之内");
            }
        }

        private static ImageHSLOffset _settings = new ImageHSLOffset();
        public static ImageHSLOffset Settings
        {
            get { return _settings; }
            set { _settings = value; }
        }

        static HSLColor()
        {          
            HueColors.Add(new ColorRange(346, 15));     //0
            HueColors.Add(new ColorRange(16, 45));      //1
            HueColors.Add(new ColorRange(46, 75));      //2
            HueColors.Add(new ColorRange(76, 105));     //3
            HueColors.Add(new ColorRange(106, 135));    //4
            HueColors.Add(new ColorRange(136, 165));    //5
            HueColors.Add(new ColorRange(166, 195));    //6
            HueColors.Add(new ColorRange(196, 225));    //7
            HueColors.Add(new ColorRange(226, 255));    //8
            HueColors.Add(new ColorRange(256, 285));    //9
            HueColors.Add(new ColorRange(286, 315));    //10
            HueColors.Add(new ColorRange(316, 345));    //11
 
            double h = 0.0D;
            for (int i = 0; i < Degree; i++)
            {
                for (int j = 0; j < Degree; j++)
                {
                    for (int k = 0; k < Degree; k++)
                    {
                        double r = (double)i / 255.0;
                        double g = (double)j / 255.0;
                        double b = (double)k / 255.0;
                        double max = Math.Max(r, Math.Max(g, b));
                        double min = Math.Min(r, Math.Min(g, b));
                        h = 0.0D;
                        // hue
                        if (max == min)
                        {
                            h = 0; // undefined
                        }
                        else if (max == r && g >= b)
                        {
                            h = 60.0 * (g - b) / (max - min);
                        }
                        else if (max == r && g < b)
                        {
                            h = 60.0 * (g - b) / (max - min) + 360.0;
                        }
                        else if (max == g)
                        {
                            h = 60.0 * (b - r) / (max - min) + 120.0;
                        }
                        else if (max == b)
                        {
                            h = 60.0 * (r - g) / (max - min) + 240.0;
                        }
                        HueMap[i, j, k] = (short)Math.Round(h, 0);
                    }
                }
            }
        }
        #endregion

        private int _r = 255;
        public Byte R
        {
            get { return (byte)_r; }
        }

        public Color Color
        {
            get
            {
                return Color.FromArgb(_r, _g, _b);
            }
        }

        private int _g = 255;
        public Byte G
        {
            get { return (byte)_g; }
        }

        private int _b = 255;
        public Byte B
        {
            get { return (byte)_b; }
        }

        /// <summary>
        /// 判断色相值是否是纯色
        /// </summary>      
        public bool IsPure
        {
            get
            {
                int index = HueColors.FindIndex(r => r.Contains(Hue));
                return index % 2 == 0;
            }          
        }

        /// <summary>
        ///  判断色相值是否是中间色
        /// </summary>
        /// <param name="hue"></param>
        /// <returns></returns>
        public bool IsMidtone
        {
            get
            {
                if (!IsGray)
                {
                    int index = HueColors.FindIndex(r => r.Contains(Hue));
                    return index % 2 == 1;
                }
                return false;
            }          
        }
      
        /// <summary>
        /// 是否等于色相主颜色
        /// </summary>
        public bool IsPrimaryChannel(PrimaryChannel ch)
        {
            if(_channel == HueCircleColors.Gray)
                return false;
            return (int)_channel / 2 == (int)ch;
        }
    
        /// <summary>
        /// 色相颜色属性(12种之一)
        /// </summary>
        private HueCircleColors _channel = HueCircleColors.Gray;
        public HueCircleColors Channel
        {
            get { return _channel; }
        }

        private ChannelHSLOffset _offsets = new ChannelHSLOffset();
        public ChannelHSLOffset Offsets
        {
            get { return _offsets; }
        }

        public int Hue
        {
            get
            {
                return HueMap[_r, _g, _b];
            }
        }

        public int this[int r, int g, int b]
        {
            get
            {
                return HueMap[r, g, b];
            }
        }

        // 对于中间色，与纯色0的相似性
        private double _sim0 = 0D;

        // 对于中间色，与纯色1的相似性
        private double _sim1 = 0D;

        /// <summary>
        /// 指示颜色是否为灰色(无颜色，包含纯黑和纯白)
        /// </summary>
        public bool IsGray
        {
            get { return _r == _g && _b == _g; }
        }

        private List<Action> listActions = new List<Action>();

        /// <summary>
        /// 初始化需要更新的通道操作
        /// </summary>
        public void InitActionArray()
        {
            listActions.Clear();
            if (!Settings.IsDefaultLightness)
            {
                listActions.Add(SetChannelLightnessOffset);
            }
            if (!Settings.IsDefaultSaturation)
            {
                listActions.Add(SetSaturationOffset);
            }
            if (!Settings.IsDefaultHue)
            {
                listActions.Add(SetHueOffset);              
            }
            if (!Settings.General.IsDefault)
            {
                listActions.Add(SetTotalOffsets);               
            }
        }

        public void SetTotalOffsets()
        {
            _offsets.SetOffsets(Settings.General);
            if (_offsets.Lightness != 0)
            {
                SetTotalLightnessOffset();
            }
            if (_offsets.Saturation != 0)
            {
                SetSaturationOffset();
            }
            if (_offsets.Hue != 0)
            {
                SetHueOffset();
            }  
        }

        public void SetHSLOffsets()
        {
            listActions.ForEach((handler) =>handler());
        }

        private void ComputeOffsets(ChannelHSLOffset opt0, ChannelHSLOffset opt1, ref double sim0, ref double sim1)
        {
            if (opt0.Hue != 0 || opt1.Hue != 0)
            {
                _offsets.Hue = (int)(opt0.Hue * sim0 + opt1.Hue * sim1);
            }
            if (opt0.Saturation != 0 || opt1.Saturation != 0)
            {
                _offsets.Saturation = (int)Math.Round(opt0.Saturation * sim0 + opt1.Saturation * sim1, 0);
            }
            if (opt0.Lightness != 0 || opt1.Lightness != 0)
            {
                _offsets.Lightness = (int)(opt0.Lightness * sim0 + opt1.Lightness * sim1);
            }
        }

        /// <summary>
        /// 计算颜色通道和实际偏移值
        /// </summary>
        public void OnComputing()
        {
            _sim0 = 0D;
            _sim1 = 0D;
            int hue = Hue;
            _offsets.Reset();
            int index = HueColors.FindIndex(r => r.Contains(hue));       
            if (_r == _g && _b == _g)
            {
                _channel = HueCircleColors.Gray;
            }
            else
            {
                _channel = (HueCircleColors)index;
            }       
            if (IsMidtone)
            {
                _sim0 = (HueColors[index].Ending - hue + 1) / 30D;                       
                _sim1 = (hue - HueColors[index].Starting) / 30D;
            }
            switch (_channel)
            {
                case HueCircleColors.Gray:                    
                    break;
                case HueCircleColors.Red:
                    _offsets.SetOffsets(Settings.Red);
                    break;
                case HueCircleColors.RedYellow:
                    ComputeOffsets(Settings.Red, Settings.Yellow, ref _sim0, ref _sim1);
                    break;
                case HueCircleColors.Yellow:
                    _offsets.SetOffsets(Settings.Yellow);
                    break;
                case HueCircleColors.YellowGreen:
                    ComputeOffsets(Settings.Yellow, Settings.Green, ref _sim0, ref _sim1);
                    break;
                case HueCircleColors.Green:
                    _offsets.SetOffsets(Settings.Green);
                    break;
                case HueCircleColors.GreenCyan:
                    ComputeOffsets(Settings.Green, Settings.Cyan, ref _sim0, ref _sim1);
                    break;
                case HueCircleColors.Cyan:
                    _offsets.SetOffsets(Settings.Cyan);
                    break;
                case HueCircleColors.CyanBlue:
                    ComputeOffsets(Settings.Cyan, Settings.Blue, ref _sim0, ref _sim1);
                    break;
                case HueCircleColors.Blue:
                    _offsets.SetOffsets(Settings.Blue);
                    break;
                case HueCircleColors.BlueMagenta:
                    ComputeOffsets(Settings.Blue, Settings.Magenta, ref _sim0, ref _sim1);
                    break;
                case HueCircleColors.Magenta:
                    _offsets.SetOffsets(Settings.Magenta);
                    break;
                case HueCircleColors.MagentaRed:
                    ComputeOffsets(Settings.Magenta, Settings.Red, ref _sim0, ref _sim1);
                    break;
                default:
                    break;
            }                    
        }

        // _similarity0 = (HueColors[c0].D1 - hue) / 30D;
        // _similarity1 = (hue - HueColors[c1].S0 + 1) / 30D;     
        public HSLColor()
        {
            OnComputing();
        }

        public HSLColor(Color c)
        {
            _r = c.R;
            _g = c.G;
            _b = c.B;
            OnComputing();
        }

        public HSLColor(int r, int g, int b)
        {
            _r = r;
            _g = g;
            _b = b;
            OnComputing();
        }

        public void Reset(int r, int g, int b)
        {
            _r = r;
            _g = g;
            _b = b;
            OnComputing();
        }

        private int GetOffsetHueValue(int delta)
        {
            int hue = Hue;
            hue += delta;
            if (hue < 0)
            {
                hue += 360;
            }
            else if (hue > 360)
            {
                hue -= 360;
            }
            return hue;
        }

        #region HSL adjustings
        internal static void SwapRGB(ref int a, ref int b)
        {
            a += b;
            b = a - b;
            a -= b;
        }

        /// <summary>
        /// 调节全图的Hue
        /// </summary>
        /// <param name="hsl">需要调整的颜色HSL对象</param>
        /// <param name="offset">偏移量</param>
        public void SetHueOffset()
        {
            if (Offsets.Hue == 0 || IsGray)
            {
                return;
            }
            try
            {               
                int intR = R;
                int intG = G;
                int intB = B;
                if (intR < intG)
                {
                    SwapRGB(ref intR, ref intG);
                }
                if (intR < intB)
                {
                    SwapRGB(ref intR, ref intB);
                }
                if (intB > intG)
                {
                    SwapRGB(ref intB, ref intG);
                }
                int delta = intR - intB;
                if (delta <= 0)
                    return;
                int entire = intR + intB;
                if (entire == 0 || entire == 510)
                    return;
                int H = Hue, S, L = entire >> 1;
                if (L < 128)
                {
                    S = delta * 255 / entire;
                }
                else
                {
                    S = delta * 255 / (510 - entire);
                }
                H += (int)Offsets.Hue;
                if (H < 0)
                {
                    H += 360;
                }
                else if (H > 360)
                {
                    H -= 360;
                }

                int index = H / 60;
                int extra = H % 60;
                if ((index & 1) > 0)
                {
                    extra = 60 - extra;
                }
                extra = (extra * 255 + 30) / 60;
                intG = extra - (extra - 128) * (255 - S) / 255;
                int Lum = L - 128;
                if (Lum > 0)
                {
                    intG += (((255 - intG) * Lum + 64) / 128);
                }
                else if (Lum < 0)
                {
                    intG += (intG * Lum / 128);
                }
                if (intG < 0)
                {
                    intG = 0;
                }
                else if (intG > 255)
                {
                    intG = 255;
                }
                switch (index)
                {
                    case 1:
                        SwapRGB(ref intR, ref intG);
                        break;
                    case 2:
                        SwapRGB(ref intR, ref intB);
                        SwapRGB(ref intG, ref intB);
                        break;
                    case 3:
                        SwapRGB(ref intR, ref intB);
                        break;
                    case 4:
                        SwapRGB(ref intR, ref intG);
                        SwapRGB(ref intG, ref intB);
                        break;
                    case 5:
                        SwapRGB(ref intG, ref intB);
                        break;
                }
                _r = intR;
                _g = intG;
                _b = intB;
            }
            catch (Exception ex)
            {
                Log.WriteAsync(ex.ToString());
            }
        }

        /// <summary>
        /// 调整饱和度
        /// </summary>
        /// <param name="hsl"></param>
        /// <param name="delta">饱和度偏移值</param>
        public void SetSaturationOffset()
        {
            double delta = Offsets.Saturation;
            if (delta == 0)
            {
                return;
            }

            //参数处理
            double dPercent = delta / 100D;

            // 原始RGB 
            int sr = _r;
            int sg = _g;
            int sb = _b;
            int max = Math.Max(sr, Math.Max(sg, sb));
            int min = Math.Min(sr, Math.Min(sg, sb));
            double dr, dg, db;         // 结果RGB  
            double dDelta;
            double dValue;
            double dL;
            double dS;
            double dAlpha;
 
            dDelta = (double)(max - min) / 255D;
            dValue = (double)(max + min) / 255D;

            //如果该像素点是灰色 不处理
            if (0 == dDelta)
            {
                return;
            }

            //按照公式计算明度L   [0,1]
            dL = dValue / 2;

            //按照公式计算饱和度S   [0,1]
            if (dL < 0.5)
            {
                dS = dDelta / dValue;
            }
            else
            {
                dS = dDelta / (2 - dValue);
            }

            //进行饱和度调整
            if (dPercent >= 0)
            {
                if (dPercent + dS >= 1)
                {
                    dAlpha = dS;
                }
                else
                {
                    dAlpha = 1 - dPercent;
                }
                dAlpha = 1 / dAlpha - 1;
                double nTemp = dL * 255D;
                db = sb + (sb - nTemp) * dAlpha;
                dg = sg + (sg - nTemp) * dAlpha;
                dr = sr + (sr - nTemp) * dAlpha;
            }
            else
            {
                dAlpha = dPercent;
                double ratio = 1 + dAlpha;
                double dTemp = dL * 255D;
  
                db = dTemp + (sb - dTemp) * ratio;
                dg = dTemp + (sg - dTemp) * ratio;
                dr = dTemp + (sr - dTemp) * ratio;
            }
            _r = (int)Math.Round(dr, 0);
            if (_r  >  255)
            {
                _r = 255;
            }
            _g = (int)Math.Round(dg, 0);
            _b = (int)Math.Round(db, 0);
        }

        /// <summary>
        /// 设置全图明度的偏移
        /// </summary>
        public void SetTotalLightnessOffset()
        {
            double delta = Settings.General.Lightness;
            if (delta == 0)
            {
                return;
            }
            // 原始RGB 
            int sr = _r;
            int sg = _g;
            int sb = _b;
            int max = Math.Max(sr, Math.Max(sg, sb));
            int min = Math.Min(sr, Math.Min(sg, sb));
            double dr, dg, db;                                          // 结果RGB  
            if (delta > 0)
            {
                db = sb + (255 - sb) * delta / 100D;
                dg = sg + (255 - sg) * delta / 100D;
                dr = sr + (255 - sr) * delta / 100D;
            }
            else
            {
                db = sb + sb* delta / 100D;
                dg = sg + sg * delta / 100D;
                dr = sr + sr * delta / 100D;
            }
            _r = (int)Math.Round(dr, 0);
            _g = (int)Math.Round(dg, 0);
            _b = (int)Math.Round(db, 0);
        }

        /// <summary>
        /// 设置单通道亮度的偏移
        /// </summary>
        public void SetChannelLightnessOffset()
        {
            double delta = Offsets.Lightness;
            if (delta == 0)
            {
                return;
            }
            // 原始RGB 
            int sr = _r;
            int sg = _g;
            int sb = _b;
            int max = Math.Max(sr, Math.Max(sg, sb));
            int min = Math.Min(sr, Math.Min(sg, sb));
            double dr, dg, db;                                          // 结果RGB  
            if (delta > 0)
            {
                db = sb + (max - sb) * delta / 100D;
                dg = sg + (max - sg) * delta / 100D;
                dr = sr + (max - sr) * delta / 100D;
            }
            else
            {
                db = sb + (sb - min) * delta / 100D;
                dg = sg + (sg - min) * delta / 100D;
                dr = sr + (sr - min) * delta / 100D;
            }
            _r = (int)Math.Round(dr, 0);
            _g = (int)Math.Round(dg, 0);
            _b = (int)Math.Round(db, 0);
        }

        #endregion
    }
}
